//核心库，提供了语法树的生成和遍历的功能
let core = require('@babel/core');
//工具类，可能帮我们生成相应的节点
let types = require('babel-types');
let ArrowFunctionsPlugin = require('babel-plugin-transform-es2015-arrow-functions');
//presetEnv是插件的合集，里面包含了好十几个插件 let => var
//let presetEnv = require('@babel/preset-env');
let es6Code = `
const sum = (a,b)=>{
    console.log(this);
    return a+b;
}
`;
//babel插件其实只是一个JS对象，里面会有属性visitor对象
//当babel在遍历的语法树的时候，会看有没有插件里的访问器，拦截这个节点
//如果有的话就会把对应的节点路径传给此函数
//nodePath 节点的路径
//node  节点
let ArrowFunctionsPlugin2 = {
    visitor:{
        ArrowFunctionExpression(nodePath){
        //console.log(nodePath);
        //console.log(node);
            //nodePath.replaceWith
            let node = nodePath.node;
            const thisBinding =  hoistFunctionEnvironment(nodePath);
            node.type = 'FunctionExpression';
        }
    }
}
//我要把老树变成新树
//只有一树，其实就是改造老树
function hoistFunctionEnvironment(fnPath){
    fnPath.findParent2 = function(isFound){
        let found=false;
        let thisPath = this;
        do{
            let parentPath = thisPath.parentPath;
            found=isFound(parentPath);
            thisPath=parentPath;
        }while(!found);
        return thisPath;
    }
   //Program findParent的意思就是一直向上找
   const thisEnvFn = fnPath.findParent2(p=>{
       //是一个函数，不能是箭头函数 或者 是根节点也可以
       return (p.isFunction() && !p.isArrowFunctionExpression())||p.isProgram()
   });

   //找一找当前作用域哪些地方用到了this的路径
   let thisPaths = getScopeInfoInformation(fnPath);
   //声明了一个this的别名变量，默认是_this __this
   let thisBinding = '_this';
   if(thisPaths.length>0){
       //在thisEnvFn的作用域内添加一个变量，变量名_this,初始化的值为this
    thisEnvFn.scope.push({
        id:types.identifier(thisBinding),
        init:types.thisExpression()    
    });
    thisPaths.forEach(item=>{
        //创建一个_this的标识符  
        let thisBindingRef = types.identifier(thisBinding);
        //把老的路径 上的节点替换成新节点
        item.replaceWith(thisBindingRef);
    });
   }
}
function getScopeInfoInformation(fnPath){
  let thisPaths = [];
  //遍历当前path所有的子节点路径，
  //告诉 babel我请帮我遍历fnPath的子节点，遇到ThisExpression节点就执行函数，并且把对应的路径传进去 
  fnPath.traverse({
    ThisExpression(thisPath){
        thisPaths.push(thisPath);
    }
  });
  return thisPaths;
}
/**
 * 里面其实包含了所有的流程
 * 1.根据源码代老的语法树
 * 2.遍历老的语法树
 * 3.遍历的时候要找你注册的插件，找这些插件指定的访问的节点
 * 插件的核心 就是把老的语法树转成新的语法树
 * 原则 尽可能少动少改，尽可能复用原来的节点
 */
//@babel/core.transform =esprima+estraverse+escodegen
let es5Code = core.transform(es6Code,{
    //presets:[presetEnv],
    plugins:[ArrowFunctionsPlugin2]
});
console.log(es5Code.code);